package com.app.utils;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.security.MessageDigest;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Copyright (C) @2020 fgwang.660@gmail.com
 *
 * @author witt
 * @version 2.0
 * @className UtilTools
 * @date 2020-05-16 17:21
 * @description 工具类
 */
public class UtilTools {

	private UtilTools() {
	}

	/**
	 * 比较多个字符串的值是不是相等
	 *
	 * @param args 这几个参数都相同，才会返回true
	 * @return
	 */
	public static boolean compareStrIgnoreCase(String... args) {
		// 如果都是空的，那就是 相同
		//if(isEmpty(args)) return true;

		// 全部转为大写，去重复后，是不是只有一个
		return Arrays.stream(args).map(String::toUpperCase).distinct().count() == 1;
	}


	/**
	 * 将数据库表名转为大驼峰命名 --> class name
	 *
	 * @param arg
	 * @return
	 */
	public static String toUpperCamel(String arg) {
		// 如果传的是个空值，直接返回
		if (isEmpty(arg)) return arg;
		// 如果单词用_隔开
		StringBuilder result = new StringBuilder();
		if (arg.contains("_")) {
			for (String name : arg.split("_")) {
				// 每个单词转大驼峰
				result.append(wordToUpperCamel(name));
			}
		} else {
			// 直接转为大驼峰
			result.append(wordToUpperCamel(arg));
		}
		return result.toString();
	}

	/**
	 * 将使用_连接的字符串转成小驼峰命名 ，即 数据库中的字段 ，如果只有一个字符串，将全小写
	 *
	 * @param arg
	 * @return
	 */
	public static String toLowerCamel(String arg) {
		// 如果传的是个空值，直接返回
		if (isEmpty(arg)) return arg;

		StringBuilder result = new StringBuilder();
		// 如果单词用_隔开
		if (arg.contains("_")) {
			String[] names = arg.split("_");
			// 小驼峰，第一个单词全小写
			result.append(names[0].toLowerCase());
			// 后面的单词是大驼峰
			for (int i = 1; i < names.length; i++) {
				result.append(wordToUpperCamel(names[i]));
			}
		} else {
			// 单词全小写
			result.append(arg.toLowerCase());
		}
		return result.toString();
	}


	/**
	 * 判空方法,对象，字符串，数组，List，Map是否为空
	 *
	 * @param t
	 * @param <T>
	 * @return
	 */
	public static <T> boolean isEmpty(T t) {

		// 如果t是对象
		if (t == null) return true;
		// 如果t是字符串
		if (t instanceof String && ("".equals(t) || ("".equals(((String) t).trim())))) return true;
		// 如果t是数组
		if (t instanceof Object[] && ((Object[]) t).length == 0) return true;
		// 如果t是list或者Set
		if (t instanceof Collection && ((Collection) t).size() == 0) return true;
		// 如果t是Map
		return t instanceof Map && ((Map) t).size() == 0;
	}

	/**
	 * 非空判断
	 *
	 * @param t
	 * @param <T>
	 * @return
	 */
	public static <T> boolean isNotEmpty(T t) {
		return ! isEmpty(t);
	}

	/**
	 * 判空方法 , 可变长参数,多个值都必须不为空  才会返回false ,有一个为空  就是true
	 *
	 * @param t
	 * @param <T>
	 * @return 有一个为空 就是true ，全部不为空才为false
	 */
	public static final <T> boolean isAllEmpty(T... t) {
		if (isEmpty(t)) return true;

		for (T o : t) {
			if (isEmpty(o)) return true;
		}
		return false;
	}

	/**
	 * 判断一个字符串，是不是全部由字母组成
	 *
	 * @param arg
	 * @return
	 */
	public static boolean isLetter(String arg) {
		return isEmpty(arg) || arg.matches("[a-zA-Z]+");
	}

	/**
	 * 将一个单词转换成大驼峰
	 *
	 * @param word
	 * @return
	 */
	public static String wordToUpperCamel(String word) {
		return isEmpty(word) ? word : word.substring(0, 1).toUpperCase() + word.substring(1).toLowerCase();
	}

	/**
	 * 将类名转成表名的方法，以大写字母分隔，所有字母小写 _ 拼接
	 *
	 * @param clazz bean的类类型 即 XXX.class
	 * @return 表名
	 * @description 需要 类和表 名严格遵守开发规范，即，表名，列名使用全小写字母，多个单词使用_连接， 类名是表名的大驼峰格式(去除下划线，每个单词首字母大写) 属性名是列名的小驼峰格式(去除下划线，第一个单词全小写，从第二个单词开始，每个单词首字母大写)
	 */
	public static String convertBeanToTable(Class<?> clazz) {
		if (isEmpty(clazz)) throw new NullPointerException("Parameter object is empty .");
		return lowerCamelToColumn(clazz.getSimpleName());
	}

	/**
	 * 将类中所有的属性转换成列名。转换方式，以小驼峰命名法为基准，转为每个单词用_拼接的全小写字母
	 *
	 * @param clazz bean的类类型 即 XXX.class
	 * @return 列名组成的List
	 * @description 需要 类和表 名严格遵守开发规范，即，表名，列名使用全小写字母，多个单词使用_连接， 类名是表名的大驼峰格式(去除下划线，每个单词首字母大写) 属性名是列名的小驼峰格式(去除下划线，第一个单词全小写，从第二个单词开始，每个单词首字母大写)
	 */
	public static List<String> convertBeanToColumns(Class<?> clazz) {
		if (isEmpty(clazz)) throw new NullPointerException("Parameter object is empty .");

		Field[] fields = reflectAllField(clazz);

		if (isEmpty(fields)) throw new NullPointerException("The object has no attributes defined .");
		return Arrays.stream(fields)
				.filter(UtilTools::excludeField)
				.map(Field::getName)
				.map(UtilTools::lowerCamelToColumn)
				.collect(Collectors.toList());
	}

	/**
	 * 将单个属性名转成列名
	 *
	 * @param field 属性名
	 * @return 列名   null 和 "" 将原样输出
	 */
	public static String lowerCamelToColumn(String field) {
		if (isNotEmpty(field)) {
			String[] word = field.split("(?<!^)(?=[A-Z])");
			return isNotEmpty(word) ? Arrays.stream(word).map(String::toLowerCase).collect(Collectors.joining("_")) : field;
		}
		return field;
	}


	/**
	 * 将对象属性映射为表的列属性
	 *
	 * @param object 对象属性
	 * @return K 为属性名转成的列名，V为属性值 的  Map
	 */
	public static Map<String, Object> convertBeanDataToTableData(Object object) {
		// 保证引用不为空
		if (isEmpty(object)) throw new NullPointerException("Parameter object is empty .");

		// 反射获取类中所有的属性
		Field[] fields = reflectAllField(object.getClass());
		Map<String, Object> result = new HashMap<>();
		try {
			// 遍历所有的属性
			for (Field field : fields) {
				// 排除序列化id等属性
				if (! excludeField(field)) continue;
				// private 权限的属性，设置访问权限
				field.setAccessible(true);
//				Class<?> fieldType = field.getType();
				// 这个有值，就是我们需要的
				if (isNotEmpty(field.get(object))) {
					// K 为属性名转成的列名，V为属性值
					result.put(lowerCamelToColumn(field.getName()), field.get(object));
				}
			}
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		return result;
	}

	/**
	 * 数组或者可变长参数中是否包含某个元素
	 *
	 * @param numerous 数组或可变长参数
	 * @param single   包含的元素
	 * @param <H>      两个参数属于同一类型
	 * @return {@code true} is contains, {@code false} is not contains
	 */
	public static <H> Boolean contains(H[] numerous, H single) {
		for (H h : numerous) {
			if (h.equals(single)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * 查找属性
	 *
	 * @param clazz     类类型
	 * @param fieldName 属性名
	 * @return 查找到的属性
	 */
	public static Field findField(Class<?> clazz, String fieldName) {
		if (UtilTools.isEmpty(fieldName)) throw new NullPointerException("params is NULL ");
		Field[] fields = reflectAllField(clazz);
		return Arrays.stream(fields)
				.filter(field -> fieldName.equals(field.getName()))
				.findAny()
				.orElse(null);
	}


	/**
	 * 将HTTP request 传的参数 使用反射封装为对象
	 *
	 * @param request request对象
	 * @param clazz   类类型
	 * @param <T>     类
	 * @return 类对象
	 */
	public static <T> T paramMap(HttpServletRequest request, Class<T> clazz) {
		T object = null;
		try {
			// 创建类对象
			object = clazz.newInstance();

			// 获取类中所有的属性
			Field[] fields = reflectAllField(clazz);
			// 遍历每一个属性
			for (Field field : fields) {
				// 排除序列化id等属性
				if (! excludeField(field)) continue;
				// 设置属性的访问权限
				field.setAccessible(true);
				// 获取属性名
				String key = field.getName();
				// 获取属性值
				String value = request.getParameter(key);

				// 获取属性的类型，根据类型映射值
				Class<?> fieldType = field.getType();
				if (UtilTools.isNotEmpty(value)) {
					// 特殊类型映射处理
					if (Boolean.class.isAssignableFrom(fieldType)) {
						field.set(object, Integer.parseInt(value) == 1);
					} else if (Integer.class.isAssignableFrom(fieldType)) { // int类型处理
						field.set(object, Integer.parseInt(value));
					} else if (Long.class.isAssignableFrom(fieldType)) { // Long类型处理
						field.set(object, Long.parseLong(value));
					} else if (Date.class.isAssignableFrom(fieldType)) { // 日期类型处理
						try {
							value = value.replace("T", " ");
							Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(value);
							field.set(object, date);
						} catch (ParseException e) {
							e.printStackTrace();
						}
					} else { // 默认是字符串
						field.set(object, value);
					}
				}

			}
		} catch (InstantiationException | IllegalAccessException e) {
			e.printStackTrace();
		}

		return object;
	}

	/**
	 * MD5 加密算法
	 *
	 * @param character 要加密的字符串
	 * @return 加密后的字符串
	 */
	public static String MD5Encoded(String character) {
		char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
		try {
			byte[] btInput = character.getBytes();
			// 获得MD5摘要算法的 MessageDigest 对象
			MessageDigest mdInst = MessageDigest.getInstance("MD5");
			// 使用指定的字节更新摘要
			mdInst.update(btInput);
			// 获得密文
			byte[] md = mdInst.digest();
			// 把密文转换成十六进制的字符串形式
			int j = md.length;
			char[] str = new char[j * 2];
			int k = 0;
			for (byte byte0 : md) {
				str[k++] = hexDigits[byte0 >>> 4 & 0xf];
				str[k++] = hexDigits[byte0 & 0xf];
			}
			return new String(str);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return "";
	}

	/**
	 * 校验是不是不需要的属性
	 *
	 * @param field
	 * @return
	 */
	private static Boolean excludeField(Field field) {
		if (Modifier.isFinal(field.getModifiers())) return false;
		if ("serialVersionUID".equals(field.getName())) return false;
		return true;
	}

	/**
	 * 获取类的所有属性包含父类的属性，排除顶级父类Object的属性
	 *
	 * @param clazz
	 * @return
	 */
	public static Field[] reflectAllField(Class<?> clazz) {
		Set<Field> fields = new HashSet<>();
		Collections.addAll(fields, clazz.getDeclaredFields());
		Class<?> superclass = clazz.getSuperclass();
		while (! "Object".equals(superclass.getSimpleName())) {
			Collections.addAll(fields, superclass.getDeclaredFields());
			superclass = superclass.getSuperclass();
		}
		return fields.toArray(new Field[0]);
	}


	/**
	 * 获取大小写字母加数字的随机字符串
	 *
	 * @param length 字符串的长度
	 * @return 随机字符串
	 */
	public static String randomString(int length) {
		List<String> origin = new ArrayList<>();

		// 获取全部的英文大小写字母
		for (int i = 65; i < (int) 'Z'; i++) {
			origin.add(String.valueOf((char) i));
			origin.add(String.valueOf((char) (i + 32)));
		}
		// 生成0-9数字  跟字母组合在一起
		for (int i = 0; i < 10; i++) {
			origin.add(String.valueOf(i));
		}

		StringBuilder result = new StringBuilder(length);
		// 循环length次 每次随机拿到一个字母  拼接在结果后面
		for (int i = 0; i < length; i++) {
			String c = origin.get(new Random().nextInt(origin.size()));
			result.append(c);
		}
		return result.toString();
	}

	/**
	 * 常见通用类型转换
	 *
	 * @param obj    要转换的对象
	 * @param target 目标类类型
	 * @param <T>    类型
	 * @return 对象
	 */
	public static <T> T convert(Object obj, Class<T> target) {
		/*
		 * 目标类是null
		 */
		if (isEmpty(target)) throw new NullPointerException("The parameter list is empty and cannot be converted");

		/*
		 * origin is null
		 */
		if (isEmpty(obj)) return null; // obj  is  null

		/*
		 * equal
		 */
		if (obj.getClass().isAssignableFrom(target)) return (T) obj;

		/*
		 * other
		 */
		if (target.isAssignableFrom(Date.class)) {// target is java.util.Date
			if (obj instanceof LocalDateTime) {// obj is java.time.LocalDateTime
				/*
				 * java.time.LocalDateTime to java.util.Date
				 */
				return (T) Date.from(((LocalDateTime) obj).atZone(ZoneId.systemDefault()).toInstant());
			}
			/*
			 * go to java.util.Date
			 */
		} else if (target.isAssignableFrom(LocalDateTime.class)) { // target is java.time.LocalDateTime
			if (obj instanceof Timestamp) { // obj is java.sql.Timestamp
				/*
				 * java.sql.Timestamp to java.time.LocalDateTime
				 */
				return (T) ((Timestamp) obj).toLocalDateTime();
			} else if (obj instanceof Date) {// obj is java.util.Date
				/*
				 * java.util.Date  to  java.time.LocalDateTime
				 */
				return (T) LocalDateTime.ofInstant(Instant.ofEpochMilli(((Date) obj).getTime()), ZoneId.systemDefault());
			} else if (obj instanceof String) {
				/*
				 * java.lang.String to java.time.LocalDateTime
				 */
				if(((String) obj).contains("T")) return (T) LocalDateTime.parse((String) obj);
				return (T) LocalDateTime.parse((String) obj, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
			}
			/*
			 * go to java.time.LocalDateTime
			 */
		} else if (target.isAssignableFrom(Integer.class)) { // target is java.lang.Integer
			if (obj instanceof String) {// java.lang.String  to java.lang.Integer
				return (T) new Integer(Integer.parseInt(((String) obj)));
			} else if (obj instanceof Long) {// java.lang.Long  to java.lang.Integer
				return (T) new Integer(((Long) obj).intValue());
			}
			/*
			 * go to java.lang.Integer
			 */
		} else if (target.isAssignableFrom(Long.class)) {// target is java.lang.Long
			if (obj instanceof String) {// java.lang.String to java.lang.Long
				return (T) new Long(((String) obj));
			}
			/*
			 * go to java.lang.Long
			 */
		} else if (target.isAssignableFrom(Boolean.class)) {// target is java.lang.Boolean
			if (obj instanceof String) {// java.lang.String to java.lang.Boolean
				/*
				 * java.lang.String to java.lang.Boolean
				 *
				 * return true : obj is "1" or "true"
				 * return false : obj is "0" or "false" or other
				 *
				 */
				if ("true".equals(obj)) return (T) new Boolean(true);
				if ("false".equals(obj)) return (T) new Boolean(false);
				return (T) ("1".equals(obj) ? new Boolean(true) : new Boolean(false));
			} else if (obj instanceof Integer) { // java.lang.Integer to java.lang.Boolean
				return (T) (((int) obj) <= 0 ? new Boolean(false) : new Boolean(true));
			}
			/*
			 * go to java.lang.Boolean
			 */
		}
		throw new ClassCastException("No matching type found .");
	}
}
